﻿using System;
using System.Web.Mvc;
using System.IO;
using System.Text;
using System.Web.Caching;

/// <summary>
/// Alternative implementation of ActionOutputCacheAttribute that doesn't use the reflection hack
/// The disadvantage of this approach is that it reduces your control over output buffering (it has to flush the response buffer at particular times)
/// </summary>
namespace FutureVision.Website.ActionFilter
{
    public class ActionOutputCacheAttribute : ActionFilterAttribute
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="cacheDuration">By day</param>
        public ActionOutputCacheAttribute(int cacheDuration)
        {
            _cacheDuration = cacheDuration;
        }

        private int _cacheDuration;
        private string _cacheKey;
        private Stream _originalOutputStream;

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            _cacheKey = ComputeCacheKey(filterContext);
            string cachedOutput = (string)filterContext.HttpContext.Cache[_cacheKey];
            if (cachedOutput != null)
                filterContext.Result = new ContentResult { Content = cachedOutput };
            else
            {
                _originalOutputStream = filterContext.HttpContext.Response.Filter;
                filterContext.HttpContext.Response.Flush();
                filterContext.HttpContext.Response.Filter = new CapturingResponseFilter(filterContext.HttpContext.Response.Filter);
            }
        }

        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            if (_originalOutputStream != null) // Must complete the caching
            {
                filterContext.HttpContext.Response.Flush();
                CapturingResponseFilter capturingResponseFilter = (CapturingResponseFilter)filterContext.HttpContext.Response.Filter;
                filterContext.HttpContext.Response.Filter = _originalOutputStream;
                string textWritten = capturingResponseFilter.GetContents(filterContext.HttpContext.Response.ContentEncoding);
                filterContext.HttpContext.Response.Write(textWritten);

                filterContext.HttpContext.Cache.Add(_cacheKey, textWritten, null, DateTime.Now.AddDays(_cacheDuration), Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null);
            }
        }

        private string ComputeCacheKey(ActionExecutingContext filterContext)
        {
            var keyBuilder = new StringBuilder();
            foreach (var pair in filterContext.RouteData.Values)
                keyBuilder.AppendFormat("rd{0}_{1}_", pair.Key.GetHashCode(), pair.Value.GetHashCode());
            foreach (var pair in filterContext.ActionParameters)
                keyBuilder.AppendFormat("ap{0}_{1}_", pair.Key.GetHashCode(), pair.Value.GetHashCode());
            return keyBuilder.ToString();
        }

        /// <summary>
        /// CapturingResponseFilter borrowed from MVC Contrib. See the original at http://mvccontrib.googlecode.com/svn/trunk/src/MVCContrib/UI/CapturingResponseFilter.cs
        /// </summary>
        private class CapturingResponseFilter : Stream
        {
            private Stream _sink;
            private MemoryStream mem;

            public CapturingResponseFilter(Stream sink)
            {
                _sink = sink;
                mem = new MemoryStream();
            }

            // The following members of Stream must be overriden.
            public override bool CanRead
            {
                get { return true; }
            }

            public override bool CanSeek
            {
                get { return false; }
            }

            public override bool CanWrite
            {
                get { return false; }
            }

            public override long Length
            {
                get { return 0; }
            }

            public override long Position { get; set; }

            public override long Seek(long offset, SeekOrigin direction)
            {
                return 0;
            }

            public override void SetLength(long length)
            {
                _sink.SetLength(length);
            }

            public override void Close()
            {
                _sink.Close();
                mem.Close();
            }

            public override void Flush()
            {
                _sink.Flush();
            }

            public override int Read(byte[] buffer, int offset, int count)
            {
                return _sink.Read(buffer, offset, count);
            }

            // Override the Write method to filter Response to a file. 
            public override void Write(byte[] buffer, int offset, int count)
            {
                //Here we will not write to the sink b/c we want to capture

                //Write out the response to the file.
                mem.Write(buffer, 0, count);
            }

            public string GetContents(Encoding enc)
            {
                var buffer = new byte[mem.Length];
                mem.Position = 0;
                mem.Read(buffer, 0, buffer.Length);
                return enc.GetString(buffer, 0, buffer.Length);
            }
        }
    }
}